<?php
define('FORAGE_APP_PATH', dirname(__FILE__));
require_once FORAGE_APP_PATH . '/engine/Engine.php';
require_once FORAGE_APP_PATH . '/query/Query.php';
require_once FORAGE_APP_PATH . '/document/Document.php';
require_once FORAGE_APP_PATH . '/document/Field.php';
require_once FORAGE_APP_PATH . '/util/Validate.php';
require_once FORAGE_APP_PATH . '/behaviours/Behaviour.php';
require_once FORAGE_APP_PATH . '/behaviours/BehaviourHandler.php';
require_once FORAGE_APP_PATH . '/behaviours/QueryBehaviour.php';
require_once FORAGE_APP_PATH . '/behaviours/ResponseBehaviour.php';
require_once FORAGE_APP_PATH . '/behaviours/EngineBehaviour.php';

/**
 * This is the main entry point for forage.
 * 
 * @author Rob Young <rob@roryoung.co.uk>
 * @package forage
 */
class Forage
{
  const POSITION_FIRST = 'first';
  const POSITION_LAST  = 'last';
  /**
   * Instance of the engine currently being used.
   *
   * @var ForageEngine
   */
  private $engine;
  
  /**
   * Instance of the profiler if it is being used.
   *
   * @var ForageProfiler
   */
  private $profiler;

  /**
   * Methods to be profiled with the profiler.
   *
   * @var array of strings
   */
  private $profile_methods = array(
    'add'
  );

  /**
   * Named constructor for creating a Forage object from
   * a DSN.
   *
   * @param string $dsn
   * @return Forage
   */
  public static function create($dsn, $config=array())
  {
    $engine = self::createEngine($dsn, $config);
    $forage = new self($engine, $config);
    return $forage;
  }

  /**
   * Create an instance of the ForageEngine associated
   * with the provided DSN.
   *
   * @param string $dsn
   * @return ForageEngine
   */
  private static function createEngine($dsn, $config=array())
  {
    return ForageEngine::create($dsn, $config);
  }

  /**
   * Class constructor.
   * Create an instance of Forage with a given ForageEngine.
   *
   * @param ForageEngine $engine
   * @param array $config = array()
   */
  public function __construct(ForageEngine $engine, $config=array())
  {
    $this->setEngine($engine);
    if (isset($config['profile_methods'])) {
      $this->setProfileMethods($config['profile_methods']);
    }
  }

  /**
   * Magic accessor
   *
   * @param string $name
   * @return mixed
   */
  public function __get($name)
  {
    switch ($name) {
    case 'engine':
    case 'profiler':
      if (!isset($this->$name)) {
        throw new ForageException("No $name");
      }
      return $this->$name;
    }
  }

  /**
   * Set the ForageEngine to be used for this Forage
   * instance.
   *
   * @param ForageEngine $engine
   * @return void
   */
  public function setEngine(ForageEngine $engine)
  {
    $this->engine = $engine;
  }

  /**
   * Get a ForageQuery object from the engine.
   * This may be a basic ForageQuery object or
   * a custom engine one with extra behaviours.
   *
   * @return ForageQuery
   */
  public function getQuery($query_string = null)
  {
    $query = $this->engine->getQuery();
    if ($query_string) {
      $query->setQueryString($query_string);
    }
    return $query;
  }

  /**
   * Perform a user search and return a ForageResponse. Either a ForageQuery or a string can be provided,
   * if a string is provided then it is converted into a ForageQuery object internally.
   *
   * @param mixed $query
   * @return ForageResponse
   */
  public function search($query)
  {
    if (!is_object($query)) {
      $query = $this->getQuery($query);
    }
    
    $this->profilerEnterMethod('search');
    $result = $this->engine->search($query);
    $this->profilerLeaveMethod('search');
    return $result;
  }

  /**
   * allows plugin based methods
   */
  public function __call($method, $args)
  {
    if (is_callable(array($this->engine, $method))) {
      $this->profilerEnterMethod($method);
      $response = call_user_func_array(array($this->engine, $method), $args);
      $this->profilerLeaveMethod($method);
      return $response;
    }
    throw new BadMethodCallException();
  }

  /**
   * Set which methods should be investigated by the profiler.
   *
   * @param array $methods
   */
  public function setProfileMethods($methods)
  {
    if (!is_array($methods)) {
      throw new InvalidArgumentException("expecting an array");
    }
    $this->profile_methods = $methods;
  }

  /**
   * Create a ForageProfiler instance for this Forage object, this causes profiler methods to be profiled.
   *
   * @see setProfileMethods
   */
  public function startProfiler()
  {
    if (!$this->profiler) {
      require_once FORAGE_APP_PATH . '/util/Profiler.php';
      $this->profiler = new ForageProfiler();
    }
  }

  /**
   * Remove the ForageProfiler instance for this Forage objects.
   *
   * @see startProfiler
   */
  public function stopProfiler()
  {
    if ($this->profiler) {
      unset($this->profiler);
    }
  }

  /**
   * Called when entering a profilable method
   *
   * @param string $method
   */
  protected function profilerEnterMethod($method)
  {
    if ($this->profiler && in_array($method, $this->profile_methods)) {
      $this->profiler->start($method);
    }
  }

  /**
   * Called when leaving a profilable method
   *
   * @params string $method
   */
  protected function profilerLeaveMethod($method)
  {
    if ($this->profiler && in_array($method, $this->profile_methods)) {
      $this->profiler->stop($method);
    }
  }
}

/**
 * Root forage exception
 *
 * @package forage
 */
class ForageException extends Exception {}
